package net;

import net.buffer.SBytePack;
import net.buffer.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class NetClient {

    private Socket socket;
    private FixedQueue<ByteBuffer> waitReadBuffers;
    private ConcurrentQueue<CompleteArgs<ByteBuffer>> waitWriteBuffers;
    private IntPack packBuffer;
    private BiConsumer<NetClient, ByteBuffer> receiveHandler;
    private static final int MAX_TIMEOUT_COUNT = 3;
    private int timeoutCount;
    private int status;

    private BufferManager _bufferManager;
    private SBytePack _bytePack;

    public int status() {
        return this.status;
    }

    private Consumer<NetClient> statusHandler;

    public void receiveHandler(BiConsumer<NetClient, ByteBuffer> handler) {
        this.receiveHandler = handler;
    }

    public BiConsumer<NetClient, ByteBuffer> receiveHandler() {
        return this.receiveHandler;
    }

    public NetClient(String host, int port, IntPack packBuffer, int timeout) throws IOException {
        init(new Socket(host, port), packBuffer, timeout);
    }
    public NetClient(Socket socket, IntPack buffer, int timeout) {
        init(socket, buffer, timeout);
    }
    public NetClient(Socket socket, SBytePack buffer, int timeout) {
        set(socket, buffer, timeout);
    }
    private void set(Socket socket, SBytePack buffer, int timeout) {
        this.socket = socket;
        try {
            this.socket.setSoTimeout(timeout);
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
        waitReadBuffers = new FixedQueue<>(20);
        waitWriteBuffers = new ConcurrentQueue<>();
        _bytePack = buffer;
        timeoutCount = 0;
    }
    private void init(Socket socket, IntPack buffer, int timeout) {
        this.socket = socket;
        try {
            this.socket.setSoTimeout(timeout);
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
        waitReadBuffers = new FixedQueue<>(20);
        waitWriteBuffers = new ConcurrentQueue<>();
        this.packBuffer = buffer;
        this.packBuffer.setPackHandler(this::packed);
        timeoutCount = 0;
    }

    public void start()
    {
        startReceive();
    }

    private void startReceive() {
        CompletableFuture.runAsync(() -> {
            try {
                InputStream in = socket.getInputStream();
                int readCount = packBuffer.unpack(in);
                if (readCount > 0) {
                    timeoutCount = 0;
                    startReceive();
                } else {
                    status = NetStatus.DISCONNECTED;
                    if (statusHandler != null) statusHandler.accept(this);
                    close();
                }
            } catch (SocketTimeoutException e) {
                if (timeoutCount >= MAX_TIMEOUT_COUNT) {
                    status = NetStatus.TIMEOUT;
                    if (statusHandler != null) statusHandler.accept(this);
                    close();
                } else {
                    System.out.println("超时重连！");
                    startReceive();
                }
            } catch (IOException e) {
                System.out.println("IOException " + e);
                close();
            }
        });
    }

    public void close() {
        System.out.println("Close Client!");
        try {
            socket.close();
        } catch (IOException ignored) {
            System.out.println("Client close error!");
        }
        status = NetStatus.CLOSED;
        if (statusHandler != null) statusHandler.accept(this);
    }

    public void statusHandler(Consumer<NetClient> handler) {
        statusHandler = handler;
    }

    private void packed(ByteBuffer data) {
        CompletableFuture.runAsync(() -> {
            if (this.receiveHandler() != null) {
                this.receiveHandler().accept(this, data);
            } else {
                waitReadBuffers.enqueue(data);
            }
        }, PoolExecutor.executor());
    }

    public ByteBuffer read() {
        return waitReadBuffers.dequeue();
    }

    public void writeAsync(ByteBuffer data) {
        writeAsync(data, this::writeCompleted);
    }

    public void writeAsync(ByteBuffer data, Consumer<ByteBuffer> complete) {
        if (complete == null)
            throw new RuntimeException("complete not enable null. Maybe you can use writeAsync(ByteBuffer data)");

        CompletableFuture.runAsync(() -> {
            try {
                int len = data.remaining();
                ByteBuffer buffer = new ByteBuffer(len + NetConfig.LENGTH_PACK);
                buffer.write().putInt(len).fill(data).read();
                OutputStream os = socket.getOutputStream();
                buffer.get(os);
                buffer.clear();
                complete.accept(buffer);
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }, PoolExecutor.executor());
    }

    public void writeAsync(SByteBuffer data)
    {
        CompletableFuture.runAsync(() -> {
            try {
                SByteBuffer buffer = _bytePack.pack(data);
                OutputStream os = socket.getOutputStream();
                buffer.read(os);
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }, PoolExecutor.executor());
    }

    private void writeCompleted(ByteBuffer buffer) {

    }
}
